Skip to content

SubAgents

SubAgents are child agents that can be delegated complex tasks. Unlike AIFunctions (single operations) or Skills (guided workflows), SubAgents have their own reasoning capabilities and can autonomously work through multi-step problems.

What Are SubAgents?

A SubAgent is a fully-configured agent exposed as a tool:

Parent Agent                    SubAgent
┌─────────────────┐            ┌─────────────────┐
│ "Review this PR"│  ────────► │ Code Reviewer   │
│                 │            │                 │
│                 │            │ • Reads files   │
│                 │            │ • Analyzes code │
│                 │  ◄──────── │ • Returns report│
└─────────────────┘            └─────────────────┘
     delegates                    works autonomously

When to use SubAgents:

  • Tasks requiring autonomous multi-step reasoning
  • Specialized domains (code review, research, analysis)
  • Isolating complex workflows from the parent agent

Basic Usage

Mark a method with [SubAgent] and return a SubAgent object:

csharp
public class DelegationTool
{
    [SubAgent]
    public SubAgent CodeReviewer()
    {
        var config = new AgentConfig
        {
            Name = "Code Reviewer",
            SystemInstructions = @"
                You are an expert code reviewer. When given code to review:
                1. Check for bugs and logic errors
                2. Evaluate code style and readability
                3. Suggest improvements
                4. Rate overall quality (1-10)"
        };

        return SubAgentFactory.Create(
            name: "Code Review",
            description: "Reviews code for quality, bugs, and best practices",
            agentConfig: config
        );
    }
}

SubAgentFactory Methods

Create() - Stateless

Each invocation starts fresh with a new conversation thread:

csharp
public static SubAgent Create(
    string name,
    string description,
    AgentConfig agentConfig,
    params Type[] toolTypes  // Optional tools for the SubAgent
)
csharp
[SubAgent]
public SubAgent Researcher()
{
    return SubAgentFactory.Create(
        name: "Research",
        description: "Researches topics thoroughly",
        agentConfig: new AgentConfig { SystemInstructions = "..." }
    );
}

Use when: Each task is independent; no context needs to persist.

CreateStateful() - Shared Session

Maintains conversation context across multiple invocations. All calls reuse the same session, so the sub-agent accumulates memory across turns.

csharp
public static SubAgent CreateStateful(
    string name,
    string description,
    AgentConfig agentConfig,
    params Type[] toolTypes
)
csharp
[SubAgent]
public SubAgent ProjectAssistant()
{
    return SubAgentFactory.CreateStateful(
        name: "Project Assistant",
        description: "Helps with ongoing project tasks, remembers context",
        agentConfig: new AgentConfig { SystemInstructions = "..." }
    );
}

To pin the shared session to a specific branch other than "main", set SharedBranchId after creation:

csharp
[SubAgent]
public SubAgent ReviewAssistant()
{
    var subAgent = SubAgentFactory.CreateStateful(
        name: "Review Assistant",
        description: "Tracks review feedback across the session",
        agentConfig: new AgentConfig { SystemInstructions = "..." }
    );
    subAgent.SharedBranchId = "review-thread";  // uses this branch instead of "main"
    return subAgent;
}

Use when: The SubAgent needs to remember previous interactions (e.g., ongoing project work).

CreatePerSession() - Parent context inheritance

The sub-agent inherits the parent agent's current session and branch at invocation time. It sees the full conversation history up to that point, but runs in its own isolated turn and does not write back to the parent branch.

Falls back to stateless if invoked outside of a parent session context.

csharp
public static SubAgent CreatePerSession(
    string name,
    string description,
    AgentConfig agentConfig,
    params Type[] toolTypes
)
csharp
[SubAgent]
public SubAgent Summarizer()
{
    return SubAgentFactory.CreatePerSession(
        name: "Summarize Conversation",
        description: "Summarizes everything said so far in the conversation",
        agentConfig: new AgentConfig { SystemInstructions = "..." }
    );
}

Use when: The sub-agent needs context from the parent conversation to do its job (e.g., summarization, analysis of what was said, follow-up research based on the thread so far).


Session Modes Explained

ModeSession BehaviourMemoryUse Case
StatelessNew isolated session per callNoneIndependent tasks
SharedSessionReuses the same session across callsPersistentOngoing multi-turn collaboration
PerSessionInherits parent's session as read-only contextParent's history (read)Summarization, analysis, context-aware tasks

Example: All Three Modes

csharp
// Stateless: Each review is a fresh call — no context carried over
[SubAgent]
public SubAgent IndependentReviewer()
{
    return SubAgentFactory.Create(
        name: "Review Code",
        description: "Reviews a single piece of code",
        agentConfig: reviewerConfig
    );
}

// SharedSession: Remembers previous reviews across all invocations
[SubAgent]
public SubAgent ProjectReviewer()
{
    return SubAgentFactory.CreateStateful(
        name: "Project Reviewer",
        description: "Reviews code with project context, remembers past reviews",
        agentConfig: reviewerConfig
    );
}

// PerSession: Sees the parent's conversation history; useful for summarizing what was discussed
[SubAgent]
public SubAgent ConversationSummarizer()
{
    return SubAgentFactory.CreatePerSession(
        name: "Summarize",
        description: "Summarizes everything discussed so far in this conversation",
        agentConfig: summarizerConfig
    );
}

SubAgents with Tools

SubAgents can have their own tools:

csharp
[SubAgent]
public SubAgent FileAnalyzer()
{
    var config = new AgentConfig
    {
        Name = "File Analyzer",
        SystemInstructions = "Analyze files and provide insights..."
    };

    return SubAgentFactory.Create(
        name: "Analyze Files",
        description: "Analyzes files for patterns, issues, and insights",
        agentConfig: config,
        typeof(FileSystemTool),    // SubAgent can read files
        typeof(SearchTool)         // SubAgent can search
    );
}

The SubAgent only has access to the tools you explicitly provide.


Provider Inheritance

By default, SubAgents inherit the parent agent's provider (chat client). If you don't specify a Provider in the SubAgent's AgentConfig, it automatically uses the same LLM provider as the parent.

csharp
[SubAgent]
public SubAgent SimpleReviewer()
{
    // No Provider specified = inherits parent's provider
    var config = new AgentConfig
    {
        Name = "Simple Reviewer",
        SystemInstructions = "Review code for issues..."
    };

    return SubAgentFactory.Create(
        name: "Review",
        description: "Quick code review",
        agentConfig: config  // Uses parent's provider automatically
    );
}

This is convenient for most cases—your SubAgents use the same model as the parent without extra configuration.

Overriding the Provider

To use a different provider (e.g., a cheaper model for simple tasks, or a specialized model), explicitly specify Provider in the config:

csharp
[SubAgent]
public SubAgent SpecializedAnalyzer()
{
    var config = new AgentConfig
    {
        Name = "Specialized Analyzer",
        SystemInstructions = "...",
        Provider = new ProviderConfig
        {
            ProviderKey = "openai",
            ModelName = "gpt-4o-mini",  // Use cheaper model for this task
            ApiKey = Environment.GetEnvironmentVariable("OPENAI_API_KEY")
        }
    };

    return SubAgentFactory.Create(
        name: "Specialized Analysis",
        description: "Uses GPT-4o-mini for cost-effective analysis",
        agentConfig: config
    );
}

Common patterns:

  • Use a cheaper/faster model for simple SubAgent tasks
  • Use a specialized model (e.g., code-focused) for domain-specific work
  • Use a different provider entirely (e.g., parent uses Anthropic, SubAgent uses OpenAI)

Permission Behavior

SubAgents always require permission by default. This is because they can perform multiple autonomous actions.

Unlike AIFunctions where [RequiresPermission] is opt-in, SubAgents are inherently permission-required:

csharp
// No [RequiresPermission] needed - it's implicit
[SubAgent]
public SubAgent DangerousOperations()
{
    return SubAgentFactory.Create(
        name: "System Admin",
        description: "Performs system administration tasks",
        agentConfig: adminConfig,
        typeof(SystemTool)
    );
}
// User will ALWAYS be prompted before this SubAgent runs

Event Bubbling

All events from a SubAgent flow into the parent's RunAsync stream — there is no separate stream to consume. Every AgentEvent carries an ExecutionContext property you can use to identify which agent emitted it:

  • AgentName — the SubAgent's name
  • Depth — nesting level (0 = parent agent, 1 = SubAgent, 2 = sub-sub-agent)
  • AgentChain — full hierarchy, e.g. ["ParentAgent", "Code Reviewer"]
csharp
await foreach (var evt in agent.RunAsync("Review this PR", sessionId: sessionId))
{
    if (evt is AgentEvent agentEvt)
    {
        var depth = agentEvt.ExecutionContext?.Depth ?? 0;
        var name  = agentEvt.ExecutionContext?.AgentName ?? "unknown";

        switch (agentEvt)
        {
            case TextDeltaEvent delta when depth == 0:
                // Parent agent speaking
                Console.Write(delta.Text);
                break;

            case TextDeltaEvent delta when depth == 1:
                // SubAgent working — show indented
                Console.Write($"  [{name}] {delta.Text}");
                break;

            case MessageTurnFinishedEvent finished when depth == 1:
                Console.WriteLine($"\n  [{name}] done");
                break;
        }
    }
}

---

## Typed Metadata

For compile-time validation, use the generic attribute:

```csharp
public class DelegationMetadata : IToolMetadata
{
    public bool HasSpecializedAgents { get; set; }
    // Implementation...
}

public class DelegationTool
{
    [SubAgent<DelegationMetadata>]
    public SubAgent SpecialAgent()
    {
        return SubAgentFactory.Create(
            name: "Special Agent",
            description: "Specialized task handler",
            agentConfig: specialConfig
        );
    }
}

→ See 02.1.4 Tool Metadata.md for details.


Conditional SubAgents

Show or hide SubAgents based on runtime conditions:

csharp
[SubAgent]
[ConditionalSubAgent("HasSpecializedAgents")]
public SubAgent AdvancedAnalyzer()
{
    // Only visible when metadata.HasSpecializedAgents is true
    return SubAgentFactory.Create(
        name: "Advanced Analysis",
        description: "Advanced analysis capabilities",
        agentConfig: advancedConfig
    );
}

→ See 02.1.4 Tool Metadata.md for conditional registration details.


SubAgents vs Skills vs AIFunctions

AspectAIFunctionSkillSubAgent
ComplexitySingle operationMulti-step workflowAutonomous reasoning
ControlDirectGuided by instructionsDelegated
MemoryNoneNoneOptional (stateful)
ToolsParent'sParent's (referenced)Own set
ProviderParent'sParent'sConfigurable
PermissionOpt-inOpt-inAlways required

Decision guide:

  • AIFunction: "Call this function with these parameters"
  • Skill: "Follow these steps using these functions"
  • SubAgent: "Figure out how to accomplish this goal"

Best Practices

  1. Give SubAgents clear, focused purposes: A SubAgent should excel at one domain.

  2. Write detailed system instructions: SubAgents rely heavily on their instructions for autonomous work.

  3. Provide only necessary tools: Don't give SubAgents access to tools they don't need.

  4. Use stateful mode sparingly: Shared threads consume more resources; use only when context is genuinely needed.

  5. Consider provider costs: SubAgents make their own LLM calls; expensive models add up.

csharp
// Good: Focused, well-instructed, minimal tools
[SubAgent]
public SubAgent SecurityAuditor()
{
    var config = new AgentConfig
    {
        Name = "Security Auditor",
        SystemInstructions = @"
            You are a security expert. When auditing code:
            1. Check for OWASP Top 10 vulnerabilities
            2. Identify authentication/authorization issues
            3. Look for data exposure risks
            4. Provide severity ratings (Critical/High/Medium/Low)
            5. Suggest specific fixes for each issue

            Be thorough but focused. Don't report style issues."
    };

    return SubAgentFactory.Create(
        name: "Security Audit",
        description: "Audits code for security vulnerabilities",
        agentConfig: config,
        typeof(FileSystemTool)  // Only needs to read files
    );
}

// Bad: Vague purpose, overpowered
[SubAgent]
public SubAgent DoAnything()
{
    return SubAgentFactory.Create(
        name: "Helper",
        description: "Helps with stuff",
        agentConfig: new AgentConfig { SystemInstructions = "Help the user" },
        typeof(FileSystemTool),
        typeof(DatabaseTool),
        typeof(NetworkTool),
        typeof(SystemTool)  // Way too many capabilities
    );
}

Next Steps

Released under the MIT License.